/**
 * Copyright (C) 2010 Cloudfarming <info@cloudfarming.nl>
 *
 * Licensed under the Eclipse Public License - v 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nl.cloudfarming.client.planning.tactical;

import java.beans.IntrospectionException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.persistence.Query;
import javax.swing.ActionMap;
import javax.swing.text.DefaultEditorKit;
import nl.cloudfarming.client.model.CommonModelService;
import nl.cloudfarming.client.model.ProductionUnit;
import nl.cloudfarming.client.model.DataProvider;
import nl.cloudfarming.client.model.PartField;
import nl.cloudfarming.client.model.FieldPlan;
import nl.cloudfarming.client.model.Operation;
import nl.cloudfarming.client.model.Task;
import nl.cloudfarming.client.model.CropProtection;
import nl.cloudfarming.client.model.CropProtectionTask;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerUtils;
import org.openide.nodes.Node;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.openide.util.lookup.ServiceProvider;
import org.openide.util.lookup.ServiceProviders;

/**
 * Persistence service for Tactical planning
 *
 * @author Timon Veenstra
 */
@ServiceProviders(value = {
    @ServiceProvider(service = DataProvider.class),
    @ServiceProvider(service = TacticalPlanningService.class)
})
public class TacticalPlanningService extends CommonModelService {

    @Override
    protected String getModuleName() {
        return TacticalPlanningModule.MODULE_NAME;
    }

    /**
     * Updates a Operation. 
     * @param toBeMerged
     * @throws IllegalArgumentException: when toBemerged is not persisted yet.
     */
    public void updateOperation(Operation toBeMerged) {
        startTransaction();
        if (toBeMerged.getId() == 0) {
            throw new IllegalArgumentException("The Operation cannot be updated because it is not persisted yet."); 
        } else {
            toBeMerged = getEntityManager().merge(toBeMerged);
        }
        commit();
    }
    
    public Node[] getFieldplanNodes(){
        return getExplorerManager().getRootContext().getChildren().getNodes();
    }

    /**
     * updates a task
     * @param toBeMerged
     * @throws IllegalArgumentException: when toBemerged is not persisted yet.
     */
    public void updateTask(Task toBeMerged) {
        startTransaction();
        if (toBeMerged.getId() == 0) {
            throw new IllegalArgumentException("The OPeration cannot be updated because it is not persisted yet."); 
        } else {
            toBeMerged = getEntityManager().merge(toBeMerged);
        }
        commit();
    }

    /**
     * updates a productionUnit
     * @param toBeMerged
     * @throws IllegalArgumentException: when toBemerged is not persisted yet.
     */
    public void updateProductionUnit(ProductionUnit toBeMerged) {
        startTransaction();
        if (toBeMerged.getId() == 0) {
            throw new IllegalArgumentException("The OPeration cannot be updated because it is not persisted yet."); 
        } else {
            toBeMerged = getEntityManager().merge(toBeMerged);
        }
        commit();
    }

    /**
     * Creates an Operation within a transaction. Starts a transaction if it is not already started.
     * @param operation used as parameter placeholder, use return value
     * @return 
     */
    public Operation createOperation(Operation.Builder operationBuilder) {
        Operation o = operationBuilder.build();
        startTransaction();
        getEntityManager().persist(o);
        commit();
        return o;
    }
    
    /**
     * Creates an Operation within a transaction. Starts a transaction if it is not already started.
     * @param operation used as parameter
     * @return persisted Operation
     */
    public Operation createOperation(Operation operation) {
        startTransaction();
        getEntityManager().persist(operation);
        commit();
        return operation;
    }

    /**
     * Creates an Task within a transaction. Starts a transaction if it is not already started.
     * @param Task used as parameter placeholder, use return value
     * @return 
     */
    public Task createTask(Task.Builder taskBuilder) {
        Task t = taskBuilder.build();
        startTransaction();
        getEntityManager().persist(t);
        commit();
        return t;
    }

    /**
     * Creates a productionUnit within a transaction. Starts a transaction if it is not already started.
     * @param productionUnitBuilder PUBuilder to build productionUnit from.
     * @return 
     */
    public ProductionUnit createProductionUnit(ProductionUnit.Builder productionUnitBuilder) {
        ProductionUnit productionUnit = productionUnitBuilder.build();
        startTransaction();
        getEntityManager().persist(productionUnit);
        getEntityManager().getTransaction().commit();
        if (productionUnit.getFieldPlan() != null) {
            productionUnit.getFieldPlan().addProductionUnit(productionUnit);
        }
        return productionUnit;
    }
    /**
     * Creates a CropProtection within a transaction. Starts a transaction if it is not already started.
     * @param CropProtection used as parameter placeholder, use return value
     * @return 
     */
    public CropProtection createCropProtection(CropProtection.Builder cropProtectionBuilder){
        CropProtection cpb = cropProtectionBuilder.build();
        startTransaction();
        getEntityManager().persist(cpb);
        getEntityManager().getTransaction().commit();
        return cpb;
    }
    
    /**
     * Creates a CropProtectionTask within a transaction. Starts a transaction if it is not already started.
     * @param CropProtectionTask used as parameter placeholder, use return value
     * @return 
     */
    public CropProtectionTask createCropProtectionTask(CropProtectionTask.Builder cropProtectionTaskBuilder){
        CropProtectionTask cpbt = cropProtectionTaskBuilder.build();
        startTransaction();
        getEntityManager().persist(cpbt);
        getEntityManager().getTransaction().commit();
        return cpbt;
    }
    
    /**
     * Creates a CropProtectionTask within a transaction. Starts a transaction if it is not already started.
     * @param CropProtectionTask used as parameter placeholder, use return value
     * @return 
     */
    public CropProtectionTask createCropProtectionTask(CropProtectionTask cropProtectionTask){
        startTransaction();
        getEntityManager().persist(cropProtectionTask);
        getEntityManager().getTransaction().commit();
        return cropProtectionTask;
    }
            
    /**
     * Creates an FieldPlan within a transaction. Starts a transaction if it is not already started.
     * @param year for which Fieldplan is created
     */
    public FieldPlan createFieldPlanForYear(String name, int year) {
        FieldPlan fieldPlan = new FieldPlan();
        fieldPlan.setPlanYear(year);
        fieldPlan.setName(name);
        persistInTransaction(fieldPlan);
        return fieldPlan;
    }
   
    /**
	 * Todo: Merged in from base. Is this still being used?
     * find all fieldplans
     *
     * @return List of FieldPlans
     */
    public List<FieldPlan> findFieldPlans() {
        return getEntityManager().createNamedQuery(FieldPlan.Q_FIND_ALL_FIELD_PLANS).getResultList();
    }

    /**
     * Finds all available productionUnits
     * @return List of productionUnits
     */
    public List<ProductionUnit> findAllProductionUnits() {
        startTransaction();
        Query query = getEntityManager().createNamedQuery(ProductionUnit.Q_FIND_ALL_PRODUCTION_UNITS);
        return query.getResultList();
    }

    /**
     * Finds the productionUnit by the suplied id
     * @param id: the id of the productionUnit which must be found
     * @return productionUnit
     */
    ProductionUnit findProductionUnitById(Long id) {
        startTransaction();
        return getEntityManager().find(ProductionUnit.class, id);
    }

    /**
     * Finds al the available Operations
     * @return List of Operation
     */
    public List<Operation> findAllOperations() {
        startTransaction();
        Query query = getEntityManager().createNamedQuery("findAllOperations");
        return query.getResultList();
    }
    
    /**
     * Finds an Operation by its id
     * @param id: the id of the Operation which has to be found
     * @return if found Operation otherwise null 
     */
    Operation findOperationById(long id) {
        startTransaction();
        return getEntityManager().find(Operation.class, id);
    }

    /**
     * Find all the available Tasks
     * @return List Task
     */
    public List<Task> findAllTasks() {
        startTransaction();
        Query query = getEntityManager().createNamedQuery(Task.Q_FIND_ALL_TASKS);
        return query.getResultList();
    }

    /**
     * Find all the available Fields
     * @return List Field
     */
    public List<PartField> findAllPartFields() {
        startTransaction();
        Query query = getEntityManager().createNamedQuery(PartField.Q_FIND_ALL_PARTFIELDS);
        return query.getResultList();
    }

    /**
     * Finds Task by the supplied id
     * @param id: the id of the Task which has to be found
     * @return if found Task else null
     */
    Task findTaskById(Long id) {
        startTransaction();
        return getEntityManager().find(Task.class, id);
    }

    /**
     * Finds all the operations which are connected to the productionUnit via task
     * @param id the id of the productionUnit
     * @return List List of operations linked to the specified productionUnit
     */
    public List<Task> findTaskByProductionUnitId(Long id) {
        startTransaction();
        Query query = getEntityManager().createNamedQuery(Task.Q_FIND_TASK_BY_PRODUCTION_UNIT_ID);
        query.setParameter("id", id);
        return query.getResultList();
    }
    
    /**
     * Removes productionUnit from fieldplan. Checks if the productionUnit which will be removed is managed by the enttity manager
     * @param productionUnit which will be removed.
     */
    public void removeProductionUnitFromFieldPlan(ProductionUnit productionUnit) {
        startTransaction();
        if (getEntityManager().contains(productionUnit)) {
            getEntityManager().remove(productionUnit);
        } else{
            getEntityManager().remove(getEntityManager().merge(productionUnit));
        }
        if(productionUnit.getFieldPlan() != null && productionUnit.getFieldPlan().getProductionUnits() != null){    
            productionUnit.getFieldPlan().removeProductionUnit(productionUnit);
        }
        getEntityManager().getTransaction().commit();
    }

    /**
     * Find Productionunits where Date date is between startDate and endDate
     * @param date The date which must be between startDate and endDate
     * @return List<ProductionUnit> the ProductionUnits which match.
     */
    public List<ProductionUnit> findProductionUnitsByDate(Date date) {
        startTransaction();
        Query query = getEntityManager().createNamedQuery(ProductionUnit.Q_FIND_ALL_PRODUCTION_UNITS_BY_DATE);
        query.setParameter("date", date);
        return query.getResultList();
    }

    /**
     * Find all the CropProtections
     * @return List<CropProtection> all the available CropProtections
     */
    public List<CropProtection> findAllCropProtections() {
        startTransaction();
        Query query = getEntityManager().createNamedQuery(CropProtection.Q_FIND_ALL_CROPPROTECTION);
        return query.getResultList();
    }
    
    public CropProtection findCropProtectionByNameAndLastUsage(String name, Date lastUsage) {
        startTransaction();
        Query query = getEntityManager().createNamedQuery(CropProtection.Q_FIND_CROPPROTECTION_BY_NAME_AND_LASTUSAGE);
        query.setParameter("name", name);
        query.setParameter("lastusage", lastUsage);
        return (CropProtection) query.getSingleResult();
    }
    
    public List<CropProtectionTask> findAllCropProtectionTasks() {
        startTransaction();
        Query query = getEntityManager().createNamedQuery(CropProtectionTask.Q_FIND_ALL_CROPPROTECTIONTASKS);
        return query.getResultList();
    }
    
    /*
     * find all productionUnits by FieldPlan 
     *
     * @return List of FieldPlans
     */
    public List<ProductionUnit> findProductionUnitsByFieldPlan(FieldPlan fieldPlan) {
        startTransaction();
        Query query = getEntityManager().createNamedQuery(ProductionUnit.Q_FIND_ALL_PRODUCTION_UNITS_BY_FIELDPLAN);
        query.setParameter("fieldPlan", fieldPlan);
        return query.getResultList();
    }

    final public void createNewPartField(final PartField field) {
        persistInTransaction(field);
    }
        
    @Override
    protected void initExplorer() {
        try {
            ExplorerManager manager = new ExplorerManager();

            FieldPlanRootNode rootNode = new FieldPlanRootNode();
            manager.setRootContext(rootNode);
            manager.getRootContext().setDisplayName(NbBundle.getMessage(this.getClass(), "planning_explorer.rootnode"));

            ActionMap map = new ActionMap();
            map.put(DefaultEditorKit.copyAction, ExplorerUtils.actionCopy(manager));
            map.put(DefaultEditorKit.cutAction, ExplorerUtils.actionCut(manager));
            map.put(DefaultEditorKit.pasteAction, ExplorerUtils.actionPaste(manager));
            map.put("delete", ExplorerUtils.actionDelete(manager, true)); // or false
            setExplorerManager(manager, map);
        } catch (IntrospectionException ex) {
            Exceptions.printStackTrace(ex);
        }
    }
    
    
}
